﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Threading.Tasks;

using Kalman;
using Newtonsoft.Json.Linq;
using Kalman.Extensions;
using RestSharp;
using System.Security.Cryptography;
using System.IO;
using Kalman.Security;
using System.Text;
using NLog;
using QuDao;
using System.Web;
using Wechat.MiniApp;
using System.Xml;
using TencentCloud.Solar.V20181011.Models;
using TencentCloud.Vpc.V20170312.Models;

namespace Wechat.MP
{
    /// <summary>
    /// 微信公众号服务端API调用封装
    /// </summary>
    public sealed partial class WxMPClient : SingletonBase<WxMPClient>
    {
        readonly ILogger log;
        readonly AppSettings appSettings;
        readonly string appid = "wx221239bc2e7cc3cd"; //关联公众号AppID（渠道通）
        readonly string token = "q9qvpvxgg4jdr7kf";
        readonly string aesKey = "W9FLyVvwENKCFXvoFf5qFmd1gBg39rW93S93pIvBVs1";

        public WxMPClient() : base()
        {
            //公众号在线接口调试：https://mp.weixin.qq.com/debug/
            log = LogManager.GetLogger("WxMP");
        }

        /// <summary>
        /// 刷新微信公众号服务端API访问令牌
        /// </summary>
        public WxApp RefreshAccessToken()
        {
            var app = WxAppBO.Instance.GetItem(appid);
            /* 获取access_token接口地址：https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
                     * access_token是公众号的全局唯一接口调用凭据，公众号调用各接口时都需使用access_token。开发者需要进行妥善保存。access_token的存储至少要保留512个字符空间。access_token的有效期目前为2个小时，需定时刷新，重复获取将导致上次获取的access_token失效。

                       公众平台的API调用所需的access_token的使用及生成方式说明：
                        1、为了保密appsecrect，第三方需要一个access_token获取和刷新的中控服务器。而其他业务逻辑服务器所使用的access_token均来自于该中控服务器，不应该各自去刷新，否则会造成access_token覆盖而影响业务；
                        2、目前access_token的有效期通过返回的expire_in来传达，目前是7200秒之内的值。中控服务器需要根据这个有效时间提前去刷新新access_token。在刷新过程中，中控服务器对外输出的依然是老access_token，此时公众平台后台会保证在刷新短时间内，新老access_token都可用，这保证了第三方业务的平滑过渡；
                        3、access_token的有效时间可能会在未来有调整，所以中控服务器不仅需要内部定时主动刷新，还需要提供被动刷新access_token的接口，这样便于业务服务器在API调用获知access_token已超时的情况下，可以触发access_token的刷新流程。
                 
                     * 公众号可以使用AppID和AppSecret调用本接口来获取access_token。AppID和AppSecret可在微信公众平台官网-开发页中获得（需要已经成为开发者，且帐号没有异常状态）。注意调用所有微信接口时均需使用https协议。如果第三方不使用中控服务器，而是选择各个业务逻辑点各自去刷新access_token，那么就可能会产生冲突，导致服务不稳定。
                     */
            var url = string.Format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={0}&secret={1}", app.WxAppID, app.WxAppKey);
            var client = new RestClient();
            var request = new RestRequest(url, Method.GET);

            // 成功：{"access_token":"ACCESS_TOKEN","expires_in":7200}，失败：{"errcode":40013,"errmsg":"invalid appid"}
            var response = client.Execute(request);
            if (!response.IsSuccessful)
            {
                throw new AppException($"获取微信公众号服务端API访问令牌失败：{response.ErrorMessage}");
            }

            var jo = JObject.Parse(response.Content);
            if (jo.ContainsKey("errcode"))
            {
                var errorCode = jo["errcode"].ToObject<int>();
                var errorMsg = jo["errmsg"].ToString();
                if (errorCode != 0)
                {
                    throw new AppException($"获取微信公众号服务端API访问令牌失败，错误信息：{errorCode}-{errorMsg}");
                }
            }

            int expires_in = jo["expires_in"].ToObject<int>();
            app.AccessToken = jo["access_token"].ToString();
            app.TokenExpiredTime = DateTime.Now.AddSeconds(expires_in);

            WxAppBO.Instance.UpdateToken(app.WxAppID, app.AccessToken, app.TokenExpiredTime);
            return app;
        }

        /// <summary>
        /// 获取微信公众号服务端API访问令牌（为防止冲突，正式环境直接从微信服务器获取，测试环境从正式环境获取）
        /// </summary>
        /// <param name="appid">微信公众号应用ID</param>
        /// <returns></returns>
        public string GetAccessToken()
        {
            var app = WxAppBO.Instance.GetItem(appid, true);
            if (app == null) return string.Empty;
            if (app.AccessToken.IsNullOrEmpty() || DateTime.Now.AddMinutes(10) > app.TokenExpiredTime)
            {
                app = RefreshAccessToken();//令牌为空或提前十分钟刷新令牌
            }
            return app.AccessToken;
        }

        /// <summary>
        /// 获取用户列表
        /// <para>公众号可通过本接口来获取帐号的关注者列表，关注者列表由一串OpenID（加密后的微信号，每个用户对每个公众号的OpenID是唯一的）组成。一次拉取调用最多拉取10000个关注者的OpenID，可以通过多次拉取的方式来满足需求。</para>
        /// </summary>
        /// <param name="next_openid">第一个拉取的OPENID，不填默认从头开始拉取</param>
        /// <returns><![CDATA[{ "total":2, "count":2, "data":{ "openid":["OPENID1","OPENID2"] }, "next_openid":"NEXT_OPENID" } ]]></returns>
        /// <exception cref="AppException"></exception>
        public JObject GetUserList(string next_openid = "")
        {
            //接口文档：https://developers.weixin.qq.com/doc/offiaccount/User_Management/Getting_a_User_List.html
            var accessToken = GetAccessToken();
            var url = $"https://api.weixin.qq.com/cgi-bin/user/get?access_token={accessToken}&next_openid={next_openid}";
            var jo = Execute(url);
            if (jo.ContainsKey("errcode"))
            {
                var code = jo["errcode"].ToString();
                var msg = jo["errmsg"].ToString();
                throw new AppException($"{code}-{msg}");
            }
            return jo;
        }

        #region 模板消息
        private JObject SendTemplateMessage(string touser, string template_id, string appid, string urlOrPage, Dictionary<string, string> dic)
        {
            //接口文档：https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Template_Message_Interface.html#%E5%8F%91%E9%80%81%E6%A8%A1%E6%9D%BF%E6%B6%88%E6%81%AF
            var accessToken = GetAccessToken();
            var url = $"https://api.weixin.qq.com/cgi-bin/message/template/send?access_token={accessToken}";
            var body = new JObject
            {
                ["touser"] = touser,
                ["template_id"] = template_id,
                ["data"] = new JObject()
            };

            if (appid.IsNullOrEmpty())
            {
                body["url"] = urlOrPage;
            }
            else
            {
                body["miniprogram"] = new JObject { ["appid"] = appid, ["pagepath"] = urlOrPage };
            }

            foreach (var kvp in dic)
            {
                body["data"][kvp.Key] = new JObject { ["value"] = kvp.Value };
            }

            //{ "errcode": 0,  "errmsg": "ok",  "msgid": 2998338276530978820}
            var jo = Execute(url, body);
            if (jo.ContainsKey("errcode"))
            {
                var code = jo["errcode"].ToString();
                var msg = jo["errmsg"].ToString();

                if (code != "0") throw new AppException($"{code}-{msg}");
            }
            return jo;
        }

        /// <summary>
        /// 发送订单支付成功通知
        /// </summary>
        /// <param name="order">订单</param>
        /// <param name="goodsName">商品名称</param>
        /// <param name="buyerName">买家名称</param>
        /// <param name="amount">订单金额</param>
        /// <param name="orderTime">下单时间</param>
        public void SendOrderPayNotify(Order order)
        {
            if (order == null || order.Status != 2) return;

            var orderid = order.OrderID.ToString();
            var template_id = "8kaQR598LT-UTjvsoR9jLCMx54yyFYp5l-w5TcKmwfQ";
            //var pagepath = $"pages/tenantDetail/tenantDetail?{order.TID}"; //
            var dic = new Dictionary<string, string>
            {
                //完整参数类型限制参考：https://developers.weixin.qq.com/doc/offiaccount/Subscription_Messages/api.html#send%E5%8F%91%E9%80%81%E8%AE%A2%E9%98%85%E9%80%9A%E7%9F%A5
                //{ "character_string2", orderid },
                //{ "thing3", order.GoodsName.CutString(0, 20)},
                //{ "phrase18", order.ReceiverName.CutString(0, 5)},
                //{ "amount5", $"{order.OrderAmount}元"},
                //{ "time4", order.PayTime.ToString("yyyy年MM月dd日 HH:mm")},
            };

            //try
            //{
            //    var touser_list = new List<string>();
            //    if(order.ShopID > 0)
            //    {
            //        var userIDs = ShopAccountBO.Instance.GetListByShopID(order.ShopID).Select(a => a.UserID).ToArray();
            //        var list = MPUserBO.Instance.GetList<MPUserDto>(userIDs);
            //        touser_list.AddRange(list.Select(a => a.OpenID));
            //    }
            //    else
            //    {
            //        var userIDs = AccountBO.Instance.GetOptListByTID(order.TID).Select(a => a.UserID).ToArray();
            //        var list = MPUserBO.Instance.GetList<MPUserDto>(userIDs);
            //        touser_list.AddRange(list.Select(a => a.OpenID));
            //    }
            //    foreach (var touser in touser_list)
            //    {
            //        SendTemplateMessage(touser, template_id, appid, pagepath, dic);
            //    }
            //}
            //catch (Exception ex)
            //{
            //    log.Error(ex, $"发送订单支付成功通知失败，订单ID：{orderid}");
            //}

            //Utils.SendSms(order);
        }

        #endregion

        #region 消息处理

        public string HandlingMessage(string xml)
        {
            log.Trace($"接收到公众号推送消息：{xml}");

            var doc = new XmlDocument();
            doc.LoadXml(xml);

            var root = doc.DocumentElement;
            var msgType = root["MsgType"].InnerText;
            var toUser = root["ToUserName"].InnerText; //开发者微信号
            var fromUser = root["FromUserName"].InnerText; //发送方帐号（用户OpenID）


            switch (msgType)
            {
                case "text":
                    var content = root["Content"].InnerText;
                    return HandlingTextMessage(fromUser, toUser, content);
                case "event":
                    var eventName = root["Event"].InnerText;
                    return HandlingEvent(fromUser, toUser, eventName);
            }

            return string.Empty;
        }

        /// <summary>
        /// 处理文本消息
        /// </summary>
        /// <param name="fromUser">发送方帐号（用户OpenID）</param>
        /// <param name="toUser">开发者微信号</param>
        /// <param name="content"></param>
        /// <returns></returns>
        private string HandlingTextMessage(string fromUser, string toUser, string content)
        {
            var msg = string.Empty;
            try
            {
                var arr = content.Split('@');
                var action = arr[0].ToLower();
                switch (action)
                {
                    case "bdzh": //绑定账号
                        {
                            if (arr.Length != 2) throw new AppException("无效的指令");

                            var accid = ConvertUtil.ToInt64(arr[1], 0);
                            if (accid == 0) throw new AppException($"无效的账号：{arr[1]}");
                            //var user = MPUserBO.Instance.Bind(accid, fromUser);
                            msg = "运营账号已绑定";
                        }
                        break;
                    //case "123":

                    //    break;
                    default:
                        break;
                }
            }
            catch (AppException ex)
            {
                msg = ex.Message;
            }
            catch (Exception ex)
            {
                msg = "绑定运营账号出错，请联系管理员";
                log.Error(ex, "绑定运营账号出错");
            }

            if (msg != string.Empty)
            {
                var xml = GetReplyTextMessage(fromUser, toUser, msg);
                return xml;
            }
            else { return string.Empty; }
        }

        private string HandlingEvent(string fromUser, string toUser, string eventName)
        {
            var msg = string.Empty;
            switch (eventName)
            {
                //case "subscribe": //关注
                //    msg = $"欢迎您，请回复“BDZH@1000XXXX”绑定运营账号，1000XXXX为会员ID，请进入渠道通小程序，在“我的”页面查看会员ID";
                //    break;
                case "unsubscribe": //取消关注
                    break;
            }
            return GetReplyTextMessage(fromUser, toUser, msg);
        }

        /// <summary>
        /// 获取回复的文本消息内容
        /// </summary>
        /// <param name="fromUser">发送消息的用户OpenID，回复消息时作为接收方</param>
        /// <param name="toUser">开发者微信号，回复消息时作为发送方</param>
        /// <param name="content">文本消息内容</param>
        /// <returns></returns>
        private string GetReplyTextMessage(string fromUser, string toUser, string content)
        {
            var xml = @$"<xml>
                          <ToUserName><![CDATA[{fromUser}]]></ToUserName>
                          <FromUserName><![CDATA[{toUser}]]></FromUserName>
                          <CreateTime>12345678</CreateTime>
                          <MsgType><![CDATA[text]]></MsgType>
                          <Content><![CDATA[{content}]]></Content>
                        </xml>";
            return xml;
        }
        #endregion

        #region Common Method
        private JObject Execute(string url, JObject body = null)
        {
            var method = body == null ? Method.GET : Method.POST;

            var client = new RestClient();
            var request = new RestRequest(url, method)
                .AddHeader("Content-Type", "application/json")
                .AddHeader("Accept", "application/json");

            try
            {
                if (method == Method.POST)
                {
                    request.AddJsonBody(body.ToString());
                    log.Info($"{method} {url} \n {body}");
                }
                else
                {
                    log.Info($"{method} {url}");
                }

                var response = client.Execute(request);
                if (!response.IsSuccessful)
                {
                    var msg = $"错误信息：{response.ErrorMessage}，内容详情：{response.Content}";
                    throw new Exception(msg);
                }

                log.Info(response.Content);
                return JObject.Parse(response.Content);
            }
            catch (Exception ex)
            {
                log.Error(ex);
                throw;
            }
        }

        /// <summary>
        /// 微信公众号的用户敏感数据解密算法
        /// </summary>
        /// <param name="encryptedData">待解密的用户敏感数据</param>
        /// <param name="key">微信公众号用户登录返回的session_key</param>
        /// <param name="iv"></param>
        /// <returns></returns>
        public string AESDecrypt(string encryptedData, string key, string iv)
        {
            if (string.IsNullOrEmpty(encryptedData)) return string.Empty;

            var aes = new RijndaelManaged
            {
                KeySize = 128,//设置 cipher 格式 AES-128-CBC    
                Padding = PaddingMode.PKCS7,
                Mode = CipherMode.CBC,
                Key = Convert.FromBase64String(key),
                IV = Convert.FromBase64String(iv)
            };

            byte[] data = Convert.FromBase64String(encryptedData);
            ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV); //解密

            using MemoryStream msDecrypt = new MemoryStream(data);
            using CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read);
            using StreamReader srDecrypt = new StreamReader(csDecrypt);

            var result = srDecrypt.ReadToEnd();
            return result;
        }

        /// <summary>
        /// 检查消息签名是否正确（验证消息是否来自于公众号平台）
        /// </summary>
        /// <param name="nonce">随机数</param>
        /// <param name="timestamp">时间戳</param>
        /// <param name="signature">消息签名</param>
        /// <returns></returns>
        public bool CheckSignature(string nonce, string timestamp, string signature)
        {
            var str = $"{nonce}{timestamp}{token}";
            var hashString = HashCryto.GetHash2String(str, HashAlgorithmType.SHA1).ToLower();
            return hashString == signature;
        }

        #endregion

        
    }
}
